#include "stdafx.h"
#include "RedirectedIO.h"
#include "SafeMutex.h"

#include "Poco\Thread.h"
#include "Poco\ThreadPool.h"
#include "Poco\StringTokenizer.h"
#include "Poco\UnicodeConverter.h"

#include <tchar.h>
#include <io.h>
#include <FCNTL.h>
#include <atlconv.h>

namespace ExamSystem
{
    using Poco::Thread;
    using Poco::ThreadPool;

    SharedPtr<CRedirectedIO> CRedirectedIO::ms_instance = NULL;

    CRedirectedIO::CRedirectedIO(void)
        : m_synThreadStopped(false)
        , m_synIORedirected(false)
        , m_hRead(NULL)
        , m_hWrite(NULL)
    {
        m_synThreadStopped.set();
    }

    CRedirectedIO::~CRedirectedIO(void)
    {
        StopThread();
    }

    void CRedirectedIO::run()
    {
        m_synThreadStopped.reset();
        RedirectStdIO();
        m_synIORedirected.set();
        ParseStdOut();
        m_synThreadStopped.set();
    }

    void CRedirectedIO::StartThread(void)
    {
        ThreadPool::defaultPool().start(*this);
        m_synIORedirected.wait();
    }

    void CRedirectedIO::StopThread(void)
    {
        if (!m_synThreadStopped.tryWait(0))
        {
            ::CloseHandle(m_hWrite);
            ::CloseHandle(m_hRead);
            m_hWrite = NULL;
            m_hRead = NULL;
            m_synThreadStopped.wait();
        }
    }

    void CRedirectedIO::RedirectStdIO(void)
    {
        SECURITY_ATTRIBUTES security;   

        security.bInheritHandle = TRUE;   
        security.nLength = sizeof(SECURITY_ATTRIBUTES);
        security.lpSecurityDescriptor = NULL; 

        if (!::CreatePipe(&m_hRead, &m_hWrite, &security, 0))
        {
            ::MessageBox(NULL, _T("Warning! Failed to create pipe!"), _T("Warning"), MB_OK);
        }
        else
        {
            ::SetStdHandle(STD_OUTPUT_HANDLE, m_hWrite);
            ::SetStdHandle(STD_ERROR_HANDLE, m_hWrite);

            int hCrt; 
            FILE *hf; 
            hCrt = _open_osfhandle( 
                (LONG_PTR)m_hWrite, 
                _O_WTEXT 
                ); 
            hf = _fdopen( hCrt, "w" ); 
            *stdout = *hf; 
            setvbuf( stdout, NULL, _IONBF, 0 ); 
        }
    }

    void CRedirectedIO::ParseStdOut(void)
    {
        static const int bufferSize = 1024;

        char lpBuffer[bufferSize];
        DWORD nBytesRead;

        while(true)
        {         
            if (!ReadFile(m_hRead, lpBuffer, bufferSize - 1,
                &nBytesRead, NULL) || !nBytesRead)
            {
                if (GetLastError() == ERROR_BROKEN_PIPE)
                    break; // pipe done - normal exit path.
                else
                {
                    // abnormal exit
                    // TODO: should we log this?
                    break;
                }
            }
            // we have read something, write it to the string stream
            {
                CSafeScopedLock lock(m_lockStrStream);
                lpBuffer[nBytesRead] = '\0';
                m_strStream << lpBuffer;
            }
        }
    }

#ifdef _UNICODE
    int CRedirectedIO::GetLines(std::vector<std::wstring> &lines)
#else
    int CRedirectedIO::GetLines(std::vector<std::string> &lines)
#endif
    {
        CSafeScopedLock lock(m_lockStrStream);
        std::string _buf = m_strStream.str();
        m_strStream.str("");
#ifdef _UNICODE
        std::wstring buf;
        Poco::UnicodeConverter::toUTF16(_buf, buf);
#else
        std::string &buf = _buf;
#endif

        size_t posStart = buf.find_first_not_of(_T("\r\n"));
        size_t posEnd = buf.find_first_of(_T("\r\n"), posStart);
        while (posEnd != buf.npos)
        {
            // 0123 4 5678
            // abcd\r\nefg
            lines.push_back(buf.substr(posStart, posEnd - posStart));
            posStart = buf.find_first_not_of(_T("\r\n"), posEnd);
            posEnd = buf.find_first_of(_T("\r\n"), posStart);
        }

        // write rest string back to the stream
        if (posStart != buf.npos)
        {
            std::string back;
#ifdef _UNICODE
            Poco::UnicodeConverter::toUTF8(buf.substr(posStart), back);
#else
            back = buf.substr(posStart);
#endif
            m_strStream << back;
        }

        return (int)lines.size();
    }

    CRedirectedIO & CRedirectedIO::Instance(void)
    {
        if (!ms_instance)
            ms_instance = new CRedirectedIO;
        return *ms_instance;
    }

}